package com.googlecode.scrptingdoclet.doclet;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.script.ScriptException;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.googlecode.scrptingdoclet.evaluator.Evaluator;
import com.googlecode.scrptingdoclet.transformer.Transformer;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.AbstractJavaEntity;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;

public class ScriptingDoclet<T> {

	private String outDir = "target"; // default

	private String[] targetTags = new String[] { "Documented" };

	private File sourceTree;

	private Registry register;

	private String encoding;

	public void setSourceTree(File file) {
		this.sourceTree = file;
	}

	public void setSourceEncoding(String encoding) {
		this.encoding = encoding;
	}

	public void setFilter(String... tags) {
		this.targetTags = tags;
	}

	public void execute() throws IOException, InterruptedException,
			ScriptException {

		register = new Registry();
		register.setup();

		JavaDocBuilder jdb = new JavaDocBuilder();
		jdb.setEncoding(encoding);
		jdb.addSourceTree(sourceTree);

		JavaClass[] classes = jdb.getClasses();

		List<T> results = evaluate(Arrays.asList(classes), false, outDir);

		transform(results);
	}

	protected void transform(List<T> results) {
		Transformer transformer = register.getTransformer();
		transformer.execute(results);
	}

	protected List<T> evaluate(List<JavaClass> classes, boolean isPrivate,
			String file) throws IOException, InterruptedException,
			ScriptException {

		List<JavaClass> filteredClasses = filtering(classes);

		List<T> results = Lists.newArrayList();

		for (JavaClass classDoc : filteredClasses) {
			T result = parseClassDoc(classDoc);
			results.add(result);
		}

		return results;
	}

	protected T parseClassDoc(JavaClass classDoc) throws ScriptException {
		Holder<Evaluator<T>> holder = resolveEvaluator(classDoc);
		T evalClass = holder.holdObj.evalClass(classDoc, holder.script);

		for (JavaMethod method : classDoc.getMethods()) {
			holder = resolveEvaluator(method);
			holder.holdObj.evalMethod(method, holder.script, evalClass);
		}

		for (JavaField field : classDoc.getFields()) {
			holder = resolveEvaluator(field);
			holder.holdObj.evalField(field, holder.script, evalClass);
		}

		return evalClass;
	}

	private Holder<Evaluator<T>> resolveEvaluator(AbstractJavaEntity javaEntity) {
		DocletTag tag = javaEntity.getTagByName("Script");
		String engine = tag.getNamedParameter("engine");

		// get engine from @Script engine="..."
		Evaluator<T> evaluator = register.getEvaluator(engine);

		// trim <code>...</code>
		String script = trimCode(tag.getValue());

		return new Holder(evaluator, script);
	}

	private static class Holder<T> {
		public Holder(T holdObj, String script) {
			this.holdObj = holdObj;
			this.script = script;
		}

		public T holdObj;
		public String script;
	}

	private String trimCode(String text) {
		String replace = text.replace("<code>", "").replace("</code>", "");
		return replace;
	}

	/**
	 * Filtering by targetTags.
	 * 
	 * @param classes
	 * @return
	 */
	protected List<JavaClass> filtering(List<JavaClass> classes) {
		Iterable<JavaClass> filter = Iterables.filter(classes,
				new Predicate<JavaClass>() {
					public boolean apply(JavaClass t) {

						for (DocletTag tag : t.getTags()) {
							System.out.println(tag.getName());
							for (String targetTag : targetTags) {
								if (tag.getName().equals(targetTag)) {
									return true;
								}
							}
						}

						return false;
					}
				});
		return Lists.newArrayList(filter);
	}

}
